#include <string.h>

/* {{{ TEST:  merge2h */

struct tests_merge2h {
	uint8_t		row[8];
	/* last element is invalid because we do not know the next data */
	uint16_t	exp_0[7];
	uint8_t		exp_1[7];
};

static struct tests_merge2h const		TESTS_MERGE2H[] = {
	{
		.row	= { 1,   2,   5, 255, 255, 254, 0,  0 },
		.exp_0	= { 3,   7, 260, 510, 509, 254, 0 },
		.exp_1	= { 2,   4, 130, 255, 255, 127, 0 },
	}, {
		.row	= { 1,   1,   5, 255, 255, 254, 0,  0 },
		.exp_0	= { 2,   6, 260, 510, 509, 254, 0 },
		.exp_1	= { 1,   3, 130, 255, 255, 127, 0 },
	}, {
		.row	= { 0,   0,   0,   0, 254,   0,   0,  0 },
		.exp_0	= { 0,   0,   0, 254, 254,   0,   0 },
		.exp_1	= { 0,   0,   0, 127, 127,   0,   0 },
	},
};

static void _noinline_ test_merge2h(void)
{
	for (size_t i = 0; i < ARRAY_SIZE(TESTS_MERGE2H); ++i) {
		struct tests_merge2h const		*test = &(TESTS_MERGE2H[i]);

		uint8x8_t	row   = vld1_u8(test->row);
		uint16x8_t	res_0 = merge2h(row);
		uint8x8_t	res_1 = merge2h_r(row);

		uint16_t	tmp_0[8];
		uint8_t		tmp_1[8];

		vst1q_u16(tmp_0, res_0);
		vst1_u8  (tmp_1, res_1);

		if (memcmp(tmp_0, test->exp_0, sizeof test->exp_0) != 0)
			BUG();
		if (memcmp(tmp_1, test->exp_1, sizeof test->exp_1) != 0)
			BUG();
	}
}

/* }}} TEST:  merge2h */


/* {{{ TEST:  merge4d */

struct tests_merge4d {
	uint8_t		prev[8];
	uint8_t		row[8];
	uint8_t		next[8];

	/* last element is invalid because we do not know the next data */
	uint8_t		exp[7];
};

static struct tests_merge4d const		TESTS_MERGE4D[] = {
	{
		.prev	= { 1,  2, 255,  255, 255, 255, 3, 0 },
		.row	= { 1,  1,   5,  255, 255, 254, 0, 4 },
		.next	= { 0,  0, 253,  255,   0, 255, 0, 0 },
		.exp	= { 1,  2, 192,  255, 191, 191, 2 },
	}, {
		.prev	= { 1,  2, 255,  255, 255, 255, 0, 0 },
		.row	= { 1,  2,   5,  255, 255, 254, 0, 0 },
		.next	= { 0,  0, 253,  255,   0, 255, 0, 0 },
		.exp	= { 1,  2, 192,  255, 191, 191, 0 },
	},
};

static void _noinline_ test_merge4d(void)
{
	for (size_t i = 0; i < ARRAY_SIZE(TESTS_MERGE4D); ++i) {
		struct tests_merge4d const		*test = &(TESTS_MERGE4D[i]);

		uint8x8_t	prev = vld1_u8(test->prev);
		uint8x8_t	row  = vld1_u8(test->row);
		uint8x8_t	next = vld1_u8(test->next);

		uint16_t	vert_sum[8];

		for (size_t j = 0; j < ARRAY_SIZE(vert_sum); ++j)
			vert_sum[j] = test->prev[j] + test->next[j];

		uint16x8_t	vert  = vld1q_u16(vert_sum);

		/* total sum */
		uint16x8_t	res_0 = merge4d(row, vert);
		/* average */
		uint8x8_t	res_1 = merge4d_r(row, vert);

		uint16x8_t	res_2 = merge4dv(row, prev, next);
		uint8x8_t	res_3 = merge4dv_r(row, prev, next);

		uint16_t	sum_t[7];

		uint16_t	tmp_0[8];
		uint8_t		tmp_1[8];
		uint16_t	tmp_2[8];
		uint8_t		tmp_3[8];

		for (size_t j = 0; j < ARRAY_SIZE(sum_t); ++j)
			sum_t[j] = test->row[j] + test->row[j+1] + vert_sum[j];

		vst1q_u16(tmp_0, res_0);
		vst1_u8  (tmp_1, res_1);
		vst1q_u16(tmp_2, res_2);
		vst1_u8  (tmp_3, res_3);

		if (memcmp(tmp_0, sum_t, sizeof sum_t) != 0)
			BUG();
		if (memcmp(tmp_2, sum_t, sizeof sum_t) != 0)
			BUG();

		if (memcmp(tmp_1, test->exp, sizeof test->exp) != 0)
			BUG();
		if (memcmp(tmp_3, test->exp, sizeof test->exp) != 0)
			BUG();
	}
}
/* }}} TEST:  merge4d */


/* {{{ TEST:  merge4q */
struct tests_merge4q {
	uint8_t		prev[8];
	uint8_t		next[8];

	uint8_t		exp[7];
};

static struct tests_merge4q const		TESTS_MERGE4Q[] = {
	{
		.prev	= { 1,  2, 255,  255, 255, 255, 3, 0 },
		.exp	= {   1,  128, 255, 191, 191, 128, 1 },
		.next	= { 0,  0, 253,  255,   0, 255, 0, 0 },
	}, {
		.prev	= { 0,   0,   0,   0, 254,   0,   0,  0 },
		.exp	= { 0,   0,   0, 127, 127,   0,   0 },
		.next	= { 0,   0,   0,   0, 254,   0,   0,  0 },
	},
};

static void _noinline_ test_merge4q(void)
{
	for (size_t i = 0; i < ARRAY_SIZE(TESTS_MERGE4Q); ++i) {
		struct tests_merge4q const		*test = &(TESTS_MERGE4Q[i]);

		uint8x8_t	prev = vld1_u8(test->prev);
		uint8x8_t	next = vld1_u8(test->next);

		uint16x8_t	res_0 = merge4q(prev, next);
		uint8x8_t	res_1 = merge4q_r(prev, next);

		uint16_t	sum[7];
		uint16_t	tmp_0[8];
		uint8_t		tmp_1[8];

		vst1q_u16(tmp_0, res_0);
		vst1_u8  (tmp_1, res_1);

		for (size_t j = 0; j < ARRAY_SIZE(sum); ++j)
			sum[j] = (test->prev[j] + test->prev[j+1] +
				  test->next[j] + test->next[j+1]);

		if (memcmp(tmp_0, sum, sizeof sum) != 0)
			BUG();
		if (memcmp(tmp_1, test->exp, sizeof test->exp) != 0)
			BUG();
	}
}
/* }}} TEST:  merge4q */

struct tests_combine_pixels {
	uint8_t		even[8];
	uint8_t		odd[8];
	uint8_t		exp[16];
};

static struct tests_combine_pixels const	TESTS_COMBINE_PIXELS[] = {
	{
		.even	= { 0, 2, 4, 6, 8, 10, 12, 14 },
		.odd	= { 1, 3, 5, 7, 9, 11, 13, 15 },
		.exp	= { 0, 1,  2,  3,  4,  5,  6,  7,
			    8, 9, 10, 11, 12, 13, 14, 15 }
	},
};

static void _noinline_ test_combine_pixels(void)
{
	for (size_t i = 0; i < ARRAY_SIZE(TESTS_COMBINE_PIXELS); ++i) {
		struct tests_combine_pixels const	*test =
			&(TESTS_COMBINE_PIXELS[i]);

		uint8x8_t	even = vld1_u8(test->even);
		uint8x8_t	odd  = vld1_u8(test->odd);
		uint8x16_t	res  = combine_pixels(even, odd);

		uint8_t		tmp[16];

		vst1q_u8(tmp, res);

		if (memcmp(tmp, test->exp, sizeof test->exp) != 0)
			BUG();
	}
}

#if 0

struct tests_convert_pixels {
	uint8_t		g_prev[8];
	uint8_t		r_prev[8];

	uint8_t		b[8];
	uint8_t		g_b[8];

	uint8_t		g_r[8];
	uint8_t		r[8];

	uint8_t		b_next[8];
	uint8_t		g_next[8];
};

static struct tests_convert_pixels const	TESTS_CONVERT_PIXELS[] = {
	{
		.g_prev	= { 1, 1, 0, 0, 0, 0, 0, 0 },
		.r_prev	= { 2, 2, 0, 0, 0, 0, 0, 0 },

		.b	= { 3, 3, 0, 0, 0, 0, 0, 0 },
		.g_b	= { 1, 1, 0, 0, 0, 0, 0, 0 },

		.g_r	= { 1, 1, 0, 0, 0, 0, 0, 0 },
		.r	= { 1, 1, 0, 0, 0, 0, 0, 0 },

		.b_next	= { 1, 1, 0, 0, 0, 0, 0, 0 },
		.g_next	= { 2, 2, 0, 0, 0, 0, 0, 0 },
	},
};

static void _noinline_ test_convert_pixels(void)
{
	for (size_t i = 0; i < ARRAY_SIZE(TESTS_CONVERT_PIXELS); ++i) {
		struct tests_convert_pixels const		*test =
			&(TESTS_CONVERT_PIXELS[i]);

		uint8x16x4_t	row0;
		uint8x16x4_t	row1;

		convert_pixel

		uint8x8_t	even = vld1_u8(test->even);
		uint8x8_t	odd  = vld1_u8(test->odd);
		uint8x16_t	res  = convert_pixels(even, odd);

		uint8_t		tmp[16];

		vst1q_u8(tmp, res);

		if (memcmp(tmp, test->exp, sizeof test->exp) != 0)
			BUG();
	}
}

#endif

static void run_tests(void)
{
	test_merge2h();
	test_merge4d();
	test_merge4q();
	test_combine_pixels();
}
